#!/bin/bash

# This file and its contents are supplied under the terms of the
# Common Development and Distribution License ("CDDL"), version 1.0.
# You may only use this file in accordance with the terms of version
# 1.0 of the CDDL.
#
# A full copy of the text of the CDDL should have accompanied this
# source.  A copy of the CDDL is also available via the Internet at
# http://www.illumos.org/license/CDDL.

#
# Copyright 2020 Joyent, Inc.
#

set -euo pipefail

VERSION="1"

# Use xpg4 for sed and od
PATH=/usr/sbin:/usr/xpg4/bin:/usr/bin
export PATH

# The type of system we are on.  One of:
#    headnode    Triton head node
#    compute        Triton compute node
#    smartos        Standalone SmartOS
function sys_type {
    local smartos=0
    local headnode=0

    bootparams | while IFS="=" read var value; do
        if [[ "$var" == "smartos" && "$value" == "true" ]]; then
            smartos=1
        elif [[ "$var" == "headnode" && "$value" == "true" ]]; then
            headnode=1
        fi
    done

    if (( smartos == 0 && headnode == 0 )); then
        echo "compute"
    elif (( smartos == 0 && headnode == 1 )); then
        echo "headnode"
    elif (( smartos == 1 && headnode == 0 )); then
        echo "smartos"
    else
        echo "ERROR: both headnode and smartos set in bootparams" >&2
        exit 1
    fi
}

function get_kbmapi_host {
    local suffix

    # Grab the datacenter_name and dns_domain values from
    # networking.json and join the two lines with a '.'
    # using sed
    suffix=$(json -f /system/boot/networking.json datacenter_name dns_domain | \
        sed 'N;s/\n/./')
    printf "kbmapi.%s" "$suffix"
}

function http_date {
    LC_TIME=C TZ=GMT date +"%a, %d %b %Y %T %Z"
}

function piv_sign {
    local data="$1"
    printf "%s" "$data" | pivy-tool sign 9e | openssl enc -base64 -A
}

function hmac_sign {
    local data="$2"
    local key="$1"
    local key_hex

    key_hex=$(printf "%s\n" "$key" | openssl enc -base64 -d | od -An -tx1 | \
        tr -d ' \n')

    printf "%s" "$data" | \
        openssl dgst -sha256 -mac hmac -macopt hexkey:"$key_hex" | \
        awk '{ print $2 }' | xxd -r -p | openssl enc -base64 -A
}

function replace_auth_hdr {
    local guid="$1"
    local hmac="$2"
    local fmt='Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"\n'

    printf "$fmt" "$guid" "hmac-sha256" "date" "$hmac"
}

function auth_hdr {
    local sig="$1"
    local fingerprint=$(pivy-tool pubkey 9e | ssh-keygen -lf - -E md5 | \
        awk '{ print $2 }' | cut -d":" -f 2-)
    local fmt='Signature keyId="%s",algorithm="%s",headers="%s",signature="%s"\n'

    printf "$fmt" "$fingerprint" "ecdsa-sha256" "date" "$sig"
}

function get_pin {
    local guid="$1"
    local host=$(get_kbmapi_host)
    local url="http://$host/pivtokens/$guid/pin"
    local date=$(http_date)
    local sig=$(piv_sign "date: $date")
    local auth=$(auth_hdr "$sig")

    curl -sS \
        -H "Date: $date" \
        -H "Authorization: $auth" \
        "$url" | \
        json pin || exit 1
}

function register_pivtoken {
    local host=$(get_kbmapi_host)
    local url="http://$host/pivtokens"
    local date=$(http_date)
    local sig=$(piv_sign "date: $date")
    local auth=$(auth_hdr "$sig")

    cat - | curl -sS \
        -H "Date: $date" \
        -H "Content-Type: application/json" \
        -H "Authorization: $auth" \
        -d "@-" "$url" | \
    json 'recovery_tokens[0].token' 'recovery_tokens[0].template' || exit 1
}

function replace_pivtoken {
    local guid="$1"
    local rtoken="$2"
    local host=$(get_kbmapi_host)
    local url="http://$host/pivtokens/$guid/replace"
    local date=$(http_date)
    local sig=$(hmac_sign "$rtoken" "date: $date")
    local auth=$(replace_auth_hdr "$guid" "$sig")

    # We'll want to remove the json call and have kbmd parse
    # the JSON eventually
    cat - | curl -sS \
        -H "Date: $date" \
        -H "Authorization: $auth" \
        -H "Content-Type: application/json" \
	-d "@-" "$url" | \
    json 'recovery_tokens[0].token' 'recovery_tokens[0].template' || exit 1
}

function new_rtoken {
    local guid="$1"
    local host=$(get_kbmapi_host)
    local url="http://$host/pivtokens/$guid/recover"
    local date=$(http_date)
    local sig=$(piv_sign "date: $date")
    local auth=$(auth_hdr "$sig")

    curl -sS \
        -H "Date: $date" \
        -H "Authorization: $auth" \
        -H "Content-Type: application/json" \
        "$url" || exit 1
}

function post_rcfg_update {
    sysinfo -u
}

[[ -z "$1" ]] && \
    echo "No subcommand given." >&2 && \
    exit 1

case "$1" in
    version)
        echo "name=Triton KBMAPI"
        echo "version=1"
        exit 0;;
    get-pin)
        get_pin $2;;
    register-pivtoken)
        register_pivtoken;;
    replace-pivtoken)
        replace_pivtoken "$2" "$3";;
    new-rtoken)
        new_rtoken $2;;
    post-rcfg-update)
        post_rcfg_update;;
    *)
        echo "Unknown subcommand $1" >&2

    # Operators shouldn't be invoking this script directly, but we
    # provide basic usage information just to keep things from being
    # opaque.

    echo "Usage: $0 version" >&2
    echo "\t$0 get-pin GUID" >&2
    echo "\t$0 register-pivtoken" >&2
    echo "\t$0 replace-pivtoken" >&2
    echo "\t$0 new-rtoken GUID" >&2
    echo "\t$0 post-rcfg-update" >&2
        exit 1;;
esac
